The
Collect Function
The system
calls each application s Collect function whenever a performance monitor
program calls the RegQueryValueEx
Use the
following function prototype for your Collect function.
DWORD WINAPI CollectPerformanceData(
LPWSTR lpwszValue,
LPVOID *lppData,
LPDWORD lpcbBytes,
LPDWORD lpcObjectTypes);
The name
CollectPerformanceData is a place-holder for an application-defined name.
Argument |
Description |
lpwszValue |
Points to a
string specified by the performance monitor program in a call to the RegQueryValueEx
function. The string uses one of the formats described in Retrieving
Selected Data |
LppData |
On input,
points to a pointer to the location where the data is to be placed. On
successful exit, set *lppData to the next byte in the buffer available
for data, such as one byte past the last byte of your data. The data returned
must be a multiple of a DWORD in length. It must conform to the PERF_OBJECT_TYPE |
LpcbBytes |
On input,
points to a 32-bit value that specifies the size, in bytes, of the lppData
buffer. On successful exit, set *lpcbBytes to the size, in bytes, of
the data written to the lppData buffer. This must be a multiple of
sizeof(DWORD) (a multiple of 4). If the Collect function fails for any
reason, set *lpcbBytes to zero. |
lpcObjectTypes |
On
successful exit, set *lpcObjectTypes to the number of object type
definitions being returned. If the Collect function fails for any reason, it
should set *lpcObjectTypes to zero. |
If the requested data specified by lpwszValue
does not correspond to any of the object indexes or foreign computers supported
by your program, leave *lppData unchanged, and set *lpcbBytes and
*lpcObjectTypes both to zero. This indicates that no data is returned.
If your data collection is time-consuming, you should only respond to specific
requests and Costly requests. You should also lower the priority of the thread
collecting the data, so that it does not adversely affect system performance.
The Collect function must return one of the values
shown in the following table.
Return
value |
Description |
ERROR_MORE_DATA |
Indicates
that the size of the lppData buffer as specified by *lpcbBytes
is not large enough to store the data to be returned. In this case, leave *lppData
unchanged, and set *lpcbBytes and *lpcObjectTypes to zero. No
attempt is made to indicate the required buffer size, because this may change
before the next call. |
ERROR_SUCCESS |
Return this
value in all other cases, even if no data is returned or an error occurs. To
report errors other than insufficient buffer size, use the system event log,
but do not flood the event log with errors on every data collection
operation. |
To provide
more information to the user, the Collect function should write any error that
prevents the function from completing successfully in the system event log. For
more information, see Event Logging
If the
application collecting the data is running on another machine (remotely), then
the extensible counter functions are called in the context of the Winlogon
process, which handles the server side of the remote connection. This
distinction is important when troubleshooting problems that only occur
remotely. The Winlogon process may access the functions using multiple threads,
usually one per remote connection. This means that the Open procedure can be
called more than once. You should handle this in your code accordingly, so that
initialization tasks are not performed more than necessary. The Collect function
may also be called concurrently by multiple threads, so you should be careful
how you initialize and use temporary data. Static variables should only be used
if the data is intended to be shared across threads.
The
connection to the machine providing the data should be tested each time data is
requested. If the connection cannot be made, you need to return some sort of
indication. One suggestion is to provide a Status counter for the object that
indicates whether a valid connection exists. When the connection does not
exist, set the Status counter and return the last set of valid data.
For foreign computer interfaces, the opening of a
channel to the foreign computer must be done in the Collect function because
the computer name is not provided to the Open function. The performance DLL
should save a handle to the foreign computer to avoid reconnecting on each data
collection call, which would significantly slow down system performance.
Once you get
the data for a foreign computer, construct a PERF_DATA_BLOCK
Other things
that you might do in the Collect include:
Validate the shared memory
pointer and check that the Open function had successfully completed. If the
Open function failed, it should have already logged an event, so the Collect
function need not report this error.
Determine the data request
type, as described in Retrieving Selected Data . If the request is for Foreign data, it is ignored
and the Collect function should return no data. If the request is for a
specific object or list of objects, search for its index in the list of object
and counter indices. If you do not find a match, the data from this object is
not desired, so the function should return no data.
Request ownership of the mutex
for the shared memory object. Wait to get access to the data in the shared
memory block.
Estimate the size of the data
to make sure there is enough room for the data in the buffer. If the estimated
size is larger than the available size, return a status of ERROR_MORE_DATA. The
system passes this error to the thread that issued the call to RegQueryValueEx
to request the data.
Copy the performance data from
the shared memory to the performance data structure to be returned.
When all data has been
transferred from the shared memory file to the performance data buffer, release
the shared memory mutex and update the pointers and counter fields. The
updating of the pointers and counter fields is very important, to prevent an
access violation from the application, monitor, or system due to misleading
buffer length information.
After the
collect procedure returns successfully, the system performs the following tests
to try and catch logic errors in the collect procedure. The first test to fail
will generate an event log message and, in most cases, the data is discarded to
prevent any further problems due to invalid pointers. These tests are normally
enabled though they can be disabled by changing a registry variable value as
described below:
HKEY_LOCAL_MACHINE
\Software
\Microsoft
\Windows NT
\CurrentVersion
\Perflib
ExtCounterTestLevel
= Test_Level
The Test_Level
is a REG_DWORD that specifies the test level. Test level 1 requests all tests.
Test level 2 requests basic tests. Test level 3 requests no tests. The default
is test level 1.
The basic tests
performed on the data buffer are as follows:
Is the BytesLeft return
value consistent with the returned pointer? The returned value of the BytesLeft
argument to the Collect procedure is compared to the returned buffer pointer.
If all is consistent, the BytesLeft value added to the original buffer
pointer passed in to the Collect procedure should be the same as the buffer
pointer returned by the procedure. If they are not the same a warning message
is logged and the BytesLeft parameter is replaced by the value computed
by finding the difference between the buffer pointer after the function call
and the buffer pointer before the function call. This is somewhat risky, in
that it assumes the BytesLeft value is assumed to be the incorrect one,
when in fact it could be that the buffer pointer is the incorrect one.
Has the returned buffer pointer
exceeded the allocated buffer extent? The actual buffer passed to the collect
function is allocated specifically for that function call by the performance
library and contains a 1K-Byte Guard Page above and below the size indicated by
the remaining size of the user s buffer. A separate buffer is used to allow
testing of the extensible counter DLL s returned data without corrupting the
caller s buffer. If the returned buffer pointer (the pointer to the next byte
after this object s data) exceeds the size of this buffer (not including guard
pages) then the buffer is assumed to be invalid and discarded since it is too
large to be copied into the caller s buffer. This test consists of two parts.
If the buffer pointer exceeds the end of the buffer, but not the end of the
guard page then a buffer overrun error is logged. If the buffer pointer is past
the end of the guard page, then a heap error is logged since the heap that the
buffer was allocated from could have been corrupted causing other memory
errors.
Are the guard pages corrupted?
The 1K byte Guard Pages above and below the block of memory passed to the
collect procedure are initialized with a data pattern before the collect
function is called. This data pattern is checked after the collect procedure
returns. If any discrepancy is detected a buffer overrun or other memory error is assumed and the
buffer is discarded.
The following
tests are performed only if test level 1 is used.
Test object TotalByteLength
field consistency. This test walks the object(s) returned by the extensible
counter to see if the sum of the length of all the objects returned is the same
as the value of the size of the returned buffer. Since the Collect function
generally returns one or more object structures (including the instance
definitions, and counter definitions and data) the sum of each object s length
should be the same as the bytes returned. A failure here can indicate the
object is not computing the value of the TotalByteLength field
correctly. This can cause an application program using the data to fail by
having it get lost in the data structures.
Test Instance ByteLength
field consistency. This test is similar to the test above. The test walks
the list of instances in each object that returns multiple instances, to see if
the next object or end of buffer follows the last instance. As above, if an
inconsistency is detected, the buffer is discarded to prevent the application
program from crashing due to a lost pointer.